package au.com.zonski.gwlite.j2me.client;

import java.util.Vector;

import net.jscience.math.kvm.MathFP;

public class SeekerController implements Controller 
{
	public static final byte MODE_SEEK		= 0;
	public static final byte MODE_SPLIT		= 1;
	public static final byte MODE_AVOID		= 2;
	public static final byte MODE_SPIN 		= 4;
	public static final byte MODE_INTERCEPT = 8;
	
	private static final int SPIN_ANGLE_DELTA	= MathFP.PI / 16;
	
	private static final int[][] SPIN_DATA		= new int[MathFP.toInt( MathFP.div( MathFP.PI * 2, SPIN_ANGLE_DELTA ) )][3]; 
	
	static
	{
		int angle = 0;
		for( int i=0; i<SPIN_DATA.length; i++ )
		{
			int[] data = SPIN_DATA[ i ];
			data[0] = angle;
			data[1] = MathFP.sin( angle );
			data[2] = MathFP.cos( angle );
			angle += SPIN_ANGLE_DELTA;
		}
	}
	
	private int seekLayer = GWLiteCanvas.PLAYER_LEVEL;
	
	private int lastLearn = -1;
	
	public int targetX;
	public int targetY;
	private int targetTurretAngle;
	private int targetTurretGradient;
	private int targetTurretC;
	private int orthoganalTurretAngle;
	private int orthoganalTurretGradient;
	
	private boolean ignore;
	
	private int speed;
	private int speedSq;
	
	private byte mode;
	
	public int spinRadius;
	
	public SeekerController( byte mode, int speed )
	{
		this.speed = speed;
		this.mode = mode;
		this.speedSq = MathFP.mul( speed, speed );
	}
	
	public SeekerController( byte mode, int speed, int spinRadius )
	{
		this( mode, speed );
		this.spinRadius = spinRadius;
	}
	
	public void reset() 
	{
	}

	public boolean update(GWLiteCanvas gameState, GWEntity o)
	{
		// TODO have groups or something to reduce overhead and to 
		// make sure things don't overlap
		
		int speed;
		if( o.controllerData == null || (speed = ((int[])o.controllerData)[0])==0 )
		{	
			speed = (3*this.speed)/4 + Math.abs( gameState.random.nextInt() % this.speed/2 );
		}
		
		int x = o.x + o.width/2;
		int y = o.y + o.height/2;
		
		int dx = this.targetX - x;
		int dy = this.targetY - y;
		
		//int dsq = MathFP.mul( dx, dx ) + MathFP.mul( dy, dy );
		//if( dsq > this.speedSq )
		{
			if( dy == 0 )
			{
				dx = Math.max( -speed, Math.min( speed, dx ) );
			}
			else if( dx == 0 )
			{
				dy = Math.max( -speed, Math.min( speed, dy ) );
			}
			else if( Math.abs( dx ) > Math.abs( dy ) )
			{
				// use m
				int dxo = dx;
				dx = Math.max( -speed, Math.min( speed, dx ) );
				//dy = (dx * dy) / dxo;
				dy = MathFP.div( MathFP.mul( dx, dy ), dxo );
			}
			else
			{
				int dyo = dy;
				dy = Math.max( -speed, Math.min( speed, dy ) );
				//dx = (dy * dx) / dyo;
				dx = MathFP.div( MathFP.mul( dy, dx ), dyo );
			}
			//int distance = MathFP.sqrt( dsq );
			//int scale =  MathFP.div( this.speed, distance );
			//dx = MathFP.mul( scale, dx );
			//dy = MathFP.mul( scale, dy );
		}
		if( !ignore )
		{
			switch( this.mode )
			{
			case MODE_SPIN:
				{
					int[] controllerData = (int[]) o.controllerData;
					if( controllerData == null )
					{
						controllerData = new int[]{ 
								speed,
								gameState.random.nextInt() % MathFP.PI,
								o.x, o.y, 
								Math.abs( gameState.random.nextInt() % SPIN_DATA.length )  
						};
						o.controllerData = controllerData;
					}
					int spinIndex = controllerData[4];
					spinIndex = ( spinIndex + 1 ) % SPIN_DATA.length; 
					int[] data = SPIN_DATA[ spinIndex ];
					int angle = data[1];
					int cx = controllerData[2];
					int cy = controllerData[3];
					controllerData[ 2 ] += dx;
					controllerData[ 3 ] += dy;
					dy += data[1] * this.spinRadius;
					dx += data[2] * this.spinRadius;
					controllerData[ 1 ] = angle;
					controllerData[ 4 ] = spinIndex;
					o.x = cx + dx;
					o.y = cy + dy;
					//gameState.enforceBounds( o );
					break;
				}
			case MODE_AVOID:
				{
					int[] controllerData = (int[]) o.controllerData;
					if( controllerData == null )
					{
						controllerData = new int[]{speed, 2};
						o.controllerData = controllerData;
					}	
					int awareness = controllerData[1];
					// check that xi,yi is in front of the player (or the player is shooting backwards)
					int fx = x - this.targetX;
					int fy = y - this.targetY;
					int angle = -this.targetTurretAngle;
					int rx = MathFP.mul( fx, MathFP.cos( angle ) ) - MathFP.mul( MathFP.sin( angle ), fy );
					if( rx > o.width * 2 )
					{
						int co = y - MathFP.mul( x, this.orthoganalTurretGradient );
						// find x such that mp*x + cp = mo*x + co
						int xi = MathFP.div( 
								co - this.targetTurretC, 
								this.targetTurretGradient - this.orthoganalTurretGradient 
						);
						int yi = MathFP.mul( this.targetTurretGradient, xi ) + this.targetTurretC;
						
		
						//int boundary = MathFP.sqrt( MathFP.sqrt( dsq ) );
						// TODO : scale the boundary based on distance??
						int boundary = o.width * awareness;
						/*
						g.setColor( 0xFFFFFF );
						g.drawLine( MathFP.toInt(xi), MathFP.toInt(yi), MathFP.toInt( x ), MathFP.toInt( y ) );
						g.drawLine( MathFP.toInt(xi), MathFP.toInt(yi), MathFP.toInt( this.targetX ), MathFP.toInt( this.targetY ) );
						g.drawArc( MathFP.toInt(xi - boundary ), MathFP.toInt(yi - boundary ),
								MathFP.toInt( boundary * 2 ), MathFP.toInt( boundary * 2 ), 0, 360 
						);
						*/
						
						// figure out if we're close to the intersection point
						int dxi = x - xi;
						int dyi = y - yi;
						
						if( MathFP.mul( dxi, dxi ) + MathFP.mul( dyi, dyi ) < MathFP.mul(boundary, boundary) )
						{
							// avoid!!
							int adx = MathFP.mul( MathFP.cos( this.orthoganalTurretAngle ), this.speed );
							int ady = MathFP.mul( MathFP.sin( this.orthoganalTurretAngle ), this.speed );
							// if we're on the other side of the line,
							
							int fxi = x - xi;
							int fyi = y - yi;
							int anglei = -this.orthoganalTurretAngle;
							int frx = MathFP.mul( fxi, MathFP.cos( anglei ) ) - MathFP.mul( MathFP.sin( anglei ), fyi );
							if( frx >= 0 )
							{
								o.x += adx;
								o.y += ady;
							}
							else
							{
								o.x -= adx + dx;
								o.y -= ady + dy;
							}
							gameState.enforceBounds( o );
							awareness = 5;
						}
						else
						{
							o.x += dx;
							o.y += dy;
							awareness = 2;
						}
						controllerData[1] = awareness;
						break;
					}
				}
			default:
				int[] controllerData = (int[]) o.controllerData;
				if( controllerData == null )
				{
					controllerData = new int[]{speed};
					o.controllerData = controllerData;
				}	
				o.x += dx;
				o.y += dy;
				break;
			}
		}
		else
		{
			o.x += dx;
			o.y += dy;
		}
		
		return false;
	}

	public void learn(GWLiteCanvas gameState) 
	{
		if( gameState.updateNumber != this.lastLearn )
		{
			Vector targets = gameState.layers[ this.seekLayer ];
			if( targets.size() > 0 )
			{
				GWEntity target = (GWEntity) targets.elementAt( 0 );
				this.targetX = target.x + target.width/2;
				this.targetY = target.y + target.height/2;
				if( this.mode == MODE_AVOID )
				{
					this.ignore = gameState.slowing;
					if( !this.ignore )
					{
						int[] controllerData = (int[]) target.controllerData;
						int targetTurretRotation =  controllerData[ PlayerController.TURRET_ROTATION ];
						boolean error = true;
						while( error )
						{
							try
							{
								this.targetTurretGradient = MathFP.tan( targetTurretRotation );
								this.orthoganalTurretAngle = targetTurretRotation + MathFP.PI/2;
								this.orthoganalTurretGradient = MathFP.tan( this.orthoganalTurretAngle );
								error = false;
								this.targetTurretAngle = targetTurretRotation;
							}
							catch( ArithmeticException ex )
							{
								// skew it slightly so we don't get divide errors
								targetTurretRotation += MathFP.PI/32;
							}
						}
								
						this.targetTurretC = this.targetY - MathFP.mul( this.targetTurretGradient, this.targetX );
					}
				}
			}
			lastLearn = gameState.updateNumber;
		}
	}

	public boolean collision(GWLiteCanvas gameState, GWEntity controlled, GWEntity with) 
	{
		// put in an appropriate explosion
		boolean die = with.type != GWEntity.TYPE_PLAYER; 
		if( die )
		{
			gameState.explode( controlled, with.type == GWEntity.TYPE_BULLET );
			if( this.mode == MODE_SPLIT )
			{
				int[] controllerData = (int[]) controlled.controllerData;
				if( controllerData != null && controllerData.length == 1 )
				{
					// create three baby things!!
					int x = controlled.x;
					int y = controlled.y;
					int quantity = 3;
					int cx = x;
					int cy = y;
					for( int i=0; i<quantity; i++ )
					{
						int index = (SPIN_DATA.length * i) / quantity;
						int[] data = SPIN_DATA[ index ];
						int angle = data[0];
						//int angle = ( MathFP.PI * 2 * i ) / quantity;
						GWEntity baby = new GWEntity( gameState, x, y, GWEntity.TYPE_BROKEN_SQUARE );
						// center based on angle
						int by = -data[1] * this.spinRadius + cy;
						int bx = -data[2] * this.spinRadius + cx;
						baby.controllerData = new int[]{ 
								0, angle, bx, by, index 
						};
						gameState.layers[ GWEntity.TYPE_BROKEN_SQUARE ].addElement( baby );
					}
				}
			}
		}
		return die;
	}

}
